#include "renderNst.h"
//#include "fboRenderNst.h"

#include <QDebug>


#include <iostream>
#include <fstream>
#include <strstream>
#include <sstream>
#include <iomanip>
#include <string.h>
#include <cassert>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <vector>
#include <stdlib.h>

#include "core/api/NstApiEmulator.hpp"
#include "core/api/NstApiVideo.hpp"
#include "core/api/NstApiSound.hpp"
#include "core/api/NstApiInput.hpp"
#include "core/api/NstApiMachine.hpp"
#include "core/api/NstApiUser.hpp"
#include "core/api/NstApiNsf.hpp"
#include "core/api/NstApiMovie.hpp"
#include "core/api/NstApiFds.hpp"
#include "core/api/NstApiRewinder.hpp"
#include "core/api/NstApiCartridge.hpp"
#include "core/api/NstApiCheats.hpp"
#include "core/NstCrc32.hpp"
#include "core/NstChecksum.hpp"
#include "core/NstXml.hpp"
#include "settings.h"
#include "seffect.h"
#include "main.h"
#include "GL/glu.h"
#include <QOpenGLContext>
#include <QOpenGLFunctions>

using namespace Nes::Api;
using namespace LinuxNst;

Emulator emulator;
Cartridge::Database::Entry dbentry;

static RenderNst *pRderNst;

// called right before Nestopia is about to write pixels
bool NST_CALLBACK VideoLock(void* userData, Video::Output& video)
{
    video.pitch = pRderNst->Linux_LockScreen( video.pixels );
    return true; // true=lock success, false=lock failed (Nestopia will carry on but skip video)
}

// called right after Nestopia has finished writing pixels (not called if previous lock failed)
void NST_CALLBACK VideoUnlock(void* userData, Video::Output& video)
{
    pRderNst->Linux_UnlockScreen( video.pixels );
}


RenderNst::RenderNst():nsf_mode(0),
    loaded(0),
    framerate(0),
    cur_width(0),
    cur_height(0),
    cur_Rwidth(0),
    cur_Rheight(0),
    gl_w(0),
    gl_h(0),
    playing(0),
    linear_filter(false),
    intbuffer(NULL),
    pNstInFboRenderer(NULL),
    screenTexID(0),
    cNstVideo(NULL),
    cNstSound(NULL),
    cNstPads(NULL),
    sSettings(new Settings)
{
    pRderNst = this;
    void* userData = (void*) 0xDEADC0DE;

    // setup video lock/unlock callbacks
    Video::Output::lockCallback.Set( VideoLock, userData );
    Video::Output::unlockCallback.Set( VideoUnlock, userData );

    NstLoadGame("/home/tanxinhuaxt/mali.nes");
    NstPlayGame();

    //startTimer(10);

}

void RenderNst::updateEmulator()
{
    //qDebug()<<"------------\n";
    if (playing)
    {
    if (NES_SUCCEEDED(Rewinder(emulator).Enable(true)))
        {
            Rewinder(emulator).EnableSound(true);
        }
    }
    if(loaded)
    emulator.Execute(cNstVideo, cNstSound, cNstPads);
}

void RenderNst::NstLoadGame(const char* filename)
{
    // acquire interface to machine
    Cartridge::Database database( emulator );
    Machine machine( emulator );
    Nsf nsf( emulator );
    Nes::Result result;
    unsigned char *compbuffer;
    int compsize,  compoffset;
    char gamename[512], patchname[512];

    if (nsf_mode)
    {
        //Nsf nsf( emulator );

        //nsf.StopSong();

        // clear the audio buffer
        //memset(lbuf, 0, sizeof(lbuf));

        playing = 0;
    }

    // unload if necessary
    nst_unload();

    // C++ file stream
    std::ifstream file(filename, std::ios::in|std::ios::binary);

    // load game
    result = machine.Load( file, get_favored_system() );

    // failed?
    if (NES_FAILED(result))
    {
        switch (result)
        {
            case Nes::RESULT_ERR_INVALID_FILE:
                std::cout << "Invalid file\n";
                break;

            case Nes::RESULT_ERR_OUT_OF_MEMORY:
                std::cout << "Out of memory\n";
                break;

            case Nes::RESULT_ERR_CORRUPT_FILE:
                std::cout << "Corrupt or missing file\n";
                break;

            case Nes::RESULT_ERR_UNSUPPORTED_MAPPER:
                std::cout << "Unsupported mapper\n";
                break;

            case Nes::RESULT_ERR_MISSING_BIOS:
                std::cout << "Can't find disksys.rom for FDS game\n";
                break;

            default:
                std::cout << "Unknown error #" << result << "\n";
                break;
        }

        return;
    }

    if (machine.Is(Machine::DISK))
    {
        Fds fds( emulator );

        fds.InsertDisk(0, 0);
    }

    // note that something is loaded
    loaded = 1;

    // power on
    machine.Power( true ); // false = power off
}

// start playing
void RenderNst::NstPlayGame(void)
{
    // initialization
    SetupVideo();

    cNstVideo = new Video::Output;
    cNstSound = new Sound::Output;
    cNstPads  = new Input::Controllers;

    playing = 1;
}

void RenderNst::SetupVideo()
{
    // renderstate structure
    Video::RenderState renderState;
    Machine machine( emulator );
    Cartridge::Database database( emulator );
    Video::RenderState::Filter filter;
    int scalefactor = sSettings->GetScaleAmt() + 1;

    // figure out the region
    framerate = 60;
    if (sSettings->GetVideoMode() == 2)		// force PAL
    {
        machine.SetMode(Machine::PAL);
        framerate = 50;
    }
    else if (sSettings->GetVideoMode() == 1) 	// force NTSC
    {
        machine.SetMode(Machine::NTSC);
    }
    else	// auto
    {
        if (database.IsLoaded())
        {
            if (dbentry.GetSystem() == Cartridge::Profile::System::NES_PAL)
            {
                machine.SetMode(Machine::PAL);
                framerate = 50;
            }
            else
            {
                machine.SetMode(Machine::NTSC);
            }
        }
        else
        {
            machine.SetMode(machine.GetDesiredMode());
        }
    }

    // compute the major video parameters from the scaler type and scale factor
    switch (sSettings->GetScale())
    {
        case 0:	// None (no scaling unless OpenGL)
            if (sSettings->GetRenderType() == 0)
            {
                if (scalefactor > 1)
                {
                    std::cout << "Warning: raw scale factors > 1 not allowed with pure software, use OpenGL\n";
                }
                cur_width = cur_Rwidth = Video::Output::WIDTH;
                cur_height = cur_Rheight = Video::Output::HEIGHT;
            }
            else
            {
                cur_width = Video::Output::WIDTH;
                cur_height = Video::Output::HEIGHT;
                cur_Rwidth = cur_width * scalefactor;
                cur_Rheight = cur_height * scalefactor;
            }
            filter = Video::RenderState::FILTER_NONE;
            break;

        case 1: // NTSC
            if (sSettings->GetRenderType() == 0)
            {
                if (scalefactor > 1)
                {
                    std::cout << "Warning: NTSC scale factors > 1 not allowed with pure software - use OpenGL\n";
                }

                scalefactor = 1;
            }

            cur_width = Video::Output::NTSC_WIDTH;
            cur_Rwidth = cur_width * scalefactor;
            cur_height = Video::Output::HEIGHT;
            cur_Rheight = cur_height * 2 * scalefactor;
            filter = Video::RenderState::FILTER_NTSC;
            break;

        case 2: // scale x
            if (scalefactor == 4)
            {
                std::cout << "Warning: Scale x only allows scale factors of 3 or less\n";
                scalefactor = 3;	// there is no scale4x
            }

            cur_width = cur_Rwidth = Video::Output::WIDTH * scalefactor;
            cur_height = cur_Rheight = Video::Output::HEIGHT * scalefactor;

            switch (scalefactor)
            {
                case 2:
                    filter = Video::RenderState::FILTER_SCALE2X;
                    break;

                case 3:
                    filter = Video::RenderState::FILTER_SCALE3X;
                    break;

                default:
                    filter = Video::RenderState::FILTER_NONE;
                    break;
            }
            break;

        case 3: // scale HQx
            cur_width = cur_Rwidth = Video::Output::WIDTH * scalefactor;
            cur_height = cur_Rheight = Video::Output::HEIGHT * scalefactor;

            switch (scalefactor)
            {
                case 2:
                    filter = Video::RenderState::FILTER_HQ2X;
                    break;

                case 3:
                    filter = Video::RenderState::FILTER_HQ3X;
                    break;

                case 4:
                    filter = Video::RenderState::FILTER_HQ4X;
                    break;

                default:
                    filter = Video::RenderState::FILTER_NONE;
                    break;
            }
            break;
    }




    linear_filter = (sSettings->GetRenderType() == 2);


    renderState.filter = filter;
    renderState.width = cur_width;
    renderState.height = cur_height;


    //opengl_init_structures();
    renderState.bits.count = 32;
    renderState.bits.mask.r = 0x00ff0000;
    renderState.bits.mask.g = 0x0000ff00;
    renderState.bits.mask.b = 0x000000ff;

    gl_w = powerOfTwo(cur_width);
    gl_h = powerOfTwo(cur_height);
    intbuffer = malloc(renderState.bits.count * renderState.width * renderState.height);


    // allocate the intermediate render buffer
    //intbuffer = malloc(renderState.bits.count * renderState.width * renderState.height);

    // acquire the video interface
    Video video( emulator );

    // set the sprite limit
    video.EnableUnlimSprites(sSettings->GetSprlimit() ? false : true);

    // set up the NTSC type
    switch (sSettings->GetNtscMode())
    {
        case 0:	// composite
            video.SetSharpness(Video::DEFAULT_SHARPNESS_COMP);
            video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_COMP);
            video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_COMP);
            video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_COMP);
            video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_COMP);
            break;

        case 1:	// S-Video
            video.SetSharpness(Video::DEFAULT_SHARPNESS_SVIDEO);
            video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_SVIDEO);
            video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_SVIDEO);
            video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_SVIDEO);
            video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_SVIDEO);
            break;

        case 2:	// RGB
            video.SetSharpness(Video::DEFAULT_SHARPNESS_RGB);
            video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_RGB);
            video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_RGB);
            video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_RGB);
            video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_RGB);
            break;
    }

    // set the render state
    if (NES_FAILED(video.SetRenderState( renderState )))
    {
        std::cout << "NEStopia core rejected render state\n";
        ::exit(0);
    }

}

int RenderNst::openglInitStructures(QOpenGLFunctions *ptr)
{
    //int scalefactor =  sSettings->GetScaleAmt() + 1;
if(NULL == ptr)
{
    glEnable( GL_TEXTURE_2D );

    gl_w = powerOfTwo(cur_width);
    gl_h = powerOfTwo(cur_height);

    glGenTextures( 1, &screenTexID ) ;
    glBindTexture( GL_TEXTURE_2D, screenTexID ) ;
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST) ;
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ) ;

    glViewport( 0,0, cur_width, cur_height);  //c__
    glDisable( GL_DEPTH_TEST );
    glDisable( GL_ALPHA_TEST );
    glDisable( GL_BLEND );
    glDisable( GL_LIGHTING );
    glDisable( GL_TEXTURE_3D_EXT );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho(0.0, (GLdouble)cur_width, (GLdouble)cur_height, 0.0, 0.0, -1.0); //c__
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
else
{
//    ptr->glEnable( GL_TEXTURE_2D );

//    gl_w = powerOfTwo(cur_width);
//    gl_h = powerOfTwo(cur_height);

//    ptr->glGenTextures( 1, &screenTexID ) ;
//    ptr->glBindTexture( GL_TEXTURE_2D, screenTexID ) ;
//    ptr->glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST) ;
//    ptr->glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ) ;

//    ptr->glViewport( 0,0, cur_width, cur_height);  //c__
//    ptr->glDisable( GL_DEPTH_TEST );
//    ptr->glDisable( GL_ALPHA_TEST );
//    ptr->glDisable( GL_BLEND );
//    ptr->glDisable( GL_LIGHTING );
//    ptr->glDisable( GL_TEXTURE_3D_EXT );
//    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 透视修正
//    glMatrixMode( GL_PROJECTION );
//    glLoadIdentity();
//    glOrtho(0.0, (GLdouble)cur_width, (GLdouble)cur_height, 0.0, 0.0, -1.0); //c__
//    glMatrixMode(GL_MODELVIEW);
//    glLoadIdentity();
}

    return 0;
}

void RenderNst::opengl_blit(QOpenGLFunctions *ptr)
{
    double gl_blit_width = (double)cur_width / (double)gl_w;
    double gl_blit_height = (double)cur_height / (double)gl_h;




    if(NULL == ptr)
    {
        //glLoadIdentity();
        //glClearColor(0.0,0.0,0.0,0.0);

        glTexImage2D( GL_TEXTURE_2D,
                      0,
                      3,
                      gl_w, gl_h,
                      0,
                      GL_BGRA,//GL_BGRA,
                      GL_UNSIGNED_BYTE,
              intbuffer  ) ;
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存
        glLoadIdentity();
        glTranslatef(0.0,480.0,0.0);
        glClearColor(0.0,0.0,0.0,0.0);
        glRotatef(180, 1.0f, 0.0f, 0.0f);

        glBindTexture( GL_TEXTURE_2D, screenTexID ) ;
        glBegin( GL_QUADS ) ;
            glTexCoord2f(gl_blit_width, gl_blit_height); glVertex2i(cur_Rwidth, cur_Rheight);
            glTexCoord2f(gl_blit_width, 0.0f ); glVertex2i(cur_Rwidth,  0);
            glTexCoord2f(0.0f, 0.0f ); glVertex2i(0, 0);
            glTexCoord2f(0.0f, gl_blit_height); glVertex2i(0, cur_Rheight);
        glEnd();
    }
//    else
//    {

//        ptr->glTexImage2D( GL_TEXTURE_2D,
//                      0,
//                      GL_RGBA,//3,//GL_RGBA,
//                      gl_w, gl_h,
//                      0,
//                      GL_BGRA,//GL_BGRA,
//                      GL_UNSIGNED_BYTE,
//              intbuffer  ) ;

//        ptr->glBindTexture( GL_TEXTURE_2D, screenTexID ) ;

//        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存
//        glLoadIdentity();
//        glTranslatef(0.0,480.0,0.0);
//        //ptr->glClearColor(0.0,0.0,0.0,0.0);
//        glRotatef(180, 1.0f, 0.0f, 0.0f);
//        glBegin( GL_QUADS ) ;
////            glLoadIdentity();
////            glTranslatef(2.0,2.0,0.0);
//            glTexCoord2f(gl_blit_width, gl_blit_height); glVertex2i(cur_Rwidth, cur_Rheight);
//            glTexCoord2f(gl_blit_width, 0.0f ); glVertex2i(cur_Rwidth,  0);
//            glTexCoord2f(0.0f, 0.0f ); glVertex2i(0, 0);
//            glTexCoord2f(0.0f, gl_blit_height); glVertex2i(0, cur_Rheight);
//        glEnd();
//        glLoadIdentity();
//    }
}

void RenderNst::opengl_cleanup(QOpenGLFunctions *ptr)
{
        //SDL_FreeSurface( screen );
    if(NULL == ptr)
    {
        glDeleteTextures( 1, &screenTexID );
    }
//    else
//    {
//        ptr->glDeleteTextures( 1, &screenTexID );
//    }
    if (intbuffer)
    {
        free(intbuffer);
        intbuffer = NULL;
    }
}


void RenderNst::configure_savename( const char* filename )
{

}

void RenderNst::nst_unload(void)
{
    Machine machine(emulator);

    // if nothing's loaded, do nothing
    if (!loaded)
    {
        return;
    }

    // power down the emulated NES
    std::cout << "Powering down the emulated machine\n";
    machine.Power(false);

    // unload the cart
    machine.Unload();

    // erase any cheats
    //sCheatMgr->Unload();
}

Machine::FavoredSystem RenderNst::get_favored_system(void)
{
    switch (sSettings->GetPrefSystem())
    {
        case 0:
            return Machine::FAVORED_NES_NTSC;
            break;

        case 1:
            return Machine::FAVORED_NES_PAL;
            break;

        case 2:
            return Machine::FAVORED_FAMICOM;
            break;

        case 3:
            return Machine::FAVORED_DENDY;
            break;
    }

    return Machine::FAVORED_NES_NTSC;
}

int RenderNst::powerOfTwo( const int value )
{
    int result = 1;
    while ( result < value )
        result <<= 1;
    return result;
}

long RenderNst::Linux_LockScreen(void*& ptr)
{
    printf("Linux_LockScreen openGL\n");
    ptr = intbuffer;
    //return 480*512;
    return gl_w*4;
}

void RenderNst::Linux_UnlockScreen(void*)
{
        printf("cur_width = %d, cur_Rwidth = %d, cur_height = %d, cur_Rheight = %d\n", cur_width, cur_Rwidth, cur_height, cur_Rheight);
        printf("Linux_UnlockScreen openGL\n");

        //glwidget->update();
}

